package besta.moon.statement;

import java.sql.*;
import java.math.*;
import java.io.*;
import java.util.ArrayList;
import java.util.Calendar;
import java.net.URL;

import besta.moon.MoonConnection;
import besta.moon.metadata.MoonResultSetMetaData;
import besta.moon.command.CommandSelect;
import besta.moon.sql.MoonParser;
import besta.moon.sql.datatype.DateTime;
import besta.moon.sql.parser.SQLTokenizer;
import besta.moon.MoonException;
import besta.moon.sql.expression.ExpressionValue;
import besta.moon.sql.expression.Expressions;

public class MoonPreparedStatement extends MoonStatement implements PreparedStatement {

    private ArrayList batches;
    private final int top;

    public MoonPreparedStatement(MoonConnection con, String sql) throws SQLException {
        this(con, sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
    }

    public MoonPreparedStatement(MoonConnection con, String sql, int rsType,
            int rsConcurrency) throws SQLException {
        super(con, rsType, rsConcurrency);
        con.log.println(sql);
        MoonParser parser = new MoonParser();
        cmd = parser.parse(con, sql);
        top = cmd.getMaxRows();
    }

    public ResultSet executeQuery() throws SQLException {
        executeImp();
        return cmd.getQueryResult();
    }

    public int executeUpdate() throws SQLException {
        executeImp();
        return cmd.getUpdateCount();
    }

    final private void executeImp() throws SQLException {
        checkStatement();
        cmd.verifyParams();
        if (getMaxRows() != 0 && (top == -1 || top > getMaxRows())) {
            cmd.setMaxRows(getMaxRows());
        }
        cmd.execute(con, this);
    }

    public void setNull(int parameterIndex, int sqlType) throws SQLException {
        checkStatement();
        cmd.setParamValue(parameterIndex, null, SQLTokenizer.NULL);
    }

    public void setBoolean(int parameterIndex, boolean x) throws SQLException {
        checkStatement();
        cmd.setParamValue(parameterIndex, x ? Boolean.TRUE : Boolean.FALSE,
                SQLTokenizer.BOOLEAN);
    }

    public void setByte(int parameterIndex, byte x) throws SQLException {
        checkStatement();
        cmd.setParamValue(parameterIndex, Integer.valueOf(x), SQLTokenizer.TINYINT);
    }

    @Override
    public void setShort(int parameterIndex, short x) throws SQLException {
        checkStatement();
        cmd.setParamValue(parameterIndex, Integer.valueOf(x), SQLTokenizer.SMALLINT);
    }

    public void setInt(int parameterIndex, int x) throws SQLException {
        checkStatement();
        cmd.setParamValue(parameterIndex, x, SQLTokenizer.INT);
    }

    @Override
    public void setLong(int parameterIndex, long x) throws SQLException {
        checkStatement();
        cmd.setParamValue(parameterIndex, x, SQLTokenizer.BIGINT);
    }

    public void setFloat(int parameterIndex, float x) throws SQLException {
        checkStatement();
        cmd.setParamValue(parameterIndex, x, SQLTokenizer.REAL);
    }

    public void setDouble(int parameterIndex, double x) throws SQLException {
        checkStatement();
        cmd.setParamValue(parameterIndex, x, SQLTokenizer.DOUBLE);
    }

    @Override
    public void setBigDecimal(int parameterIndex, BigDecimal x)
            throws SQLException {
        checkStatement();
        cmd.setParamValue(parameterIndex, x, SQLTokenizer.DECIMAL);
    }

    public void setString(int parameterIndex, String x) throws SQLException {
        checkStatement();
        cmd.setParamValue(parameterIndex, x, SQLTokenizer.VARCHAR);
    }

    public void setBytes(int parameterIndex, byte[] x) throws SQLException {
        checkStatement();
        cmd.setParamValue(parameterIndex, x, SQLTokenizer.BINARY);
    }

    public void setDate(int parameterIndex, Date x) throws SQLException {
        checkStatement();
        cmd.setParamValue(parameterIndex, DateTime.valueOf(x),
                SQLTokenizer.DATE);
    }

    public void setTime(int parameterIndex, Time x) throws SQLException {
        checkStatement();
        cmd.setParamValue(parameterIndex, DateTime.valueOf(x),
                SQLTokenizer.TIME);
    }

    public void setTimestamp(int parameterIndex, Timestamp x)
            throws SQLException {
        checkStatement();
        cmd.setParamValue(parameterIndex, DateTime.valueOf(x),
                SQLTokenizer.TIMESTAMP);
    }

    public void setAsciiStream(int parameterIndex, InputStream x, int length)
            throws SQLException {
        checkStatement();
        cmd.setParamValue(parameterIndex, x, SQLTokenizer.LONGVARCHAR, length);
    }

    public void setUnicodeStream(int parameterIndex, InputStream x, int length)
            throws SQLException {
        checkStatement();
        /**
         * @todo: Implement this java.sql.PreparedStatement method
         */
        throw new java.lang.UnsupportedOperationException(
                "Method setUnicodeStream() not yet implemented.");
    }

    public void setBinaryStream(int parameterIndex, InputStream x, int length)
            throws SQLException {
        checkStatement();
        cmd.setParamValue(parameterIndex, x, SQLTokenizer.LONGVARBINARY, length);
    }

    public void clearParameters() throws SQLException {
        checkStatement();
        cmd.clearParams();
    }

    public void setObject(int parameterIndex, Object x, int targetSqlType,
            int scale) throws SQLException {
        checkStatement();
        // FIXME Scale to consider
        cmd.setParamValue(parameterIndex, x, -1);
    }

    public void setObject(int parameterIndex, Object x, int targetSqlType)
            throws SQLException {
        checkStatement();
        cmd.setParamValue(parameterIndex, x, -1);
    }

    public void setObject(int parameterIndex, Object x) throws SQLException {
        checkStatement();
        cmd.setParamValue(parameterIndex, x, -1);
    }

    public boolean execute() throws SQLException {
        executeImp();
        return cmd.getResultSet() != null;
    }

    public void addBatch() throws SQLException {
        checkStatement();
        try {
            final Expressions params = cmd.params;
            final int size = params.size();
            ExpressionValue[] values = new ExpressionValue[size];
            for (int i = 0; i < size; i++) {
                values[i] = (ExpressionValue) params.get(i).clone();
            }
            if (batches == null) {
                batches = new ArrayList();
            }
            batches.add(values);
        } catch (Exception e) {
            throw MoonException.createFromException(e);
        }
    }

    public void clearBatch() throws SQLException {
        checkStatement();
        if (batches != null) {
            batches.clear();
        }
    }

    public int[] executeBatch() throws BatchUpdateException {
        if (batches == null || batches.size() == 0) {
            return new int[0];
        }
        int[] result = new int[batches.size()];
        BatchUpdateException failed = null;
        for (int b = 0; b < batches.size(); b++) {
            try {
                checkStatement();
                ExpressionValue[] values = (ExpressionValue[]) batches.get(b);
                for (int i = 0; i < values.length; i++) {
                    ((ExpressionValue) cmd.params.get(i)).set(values[i]);
                }
                result[b] = executeUpdate();
            } catch (SQLException ex) {
                result[b] = EXECUTE_FAILED;
                if (failed == null) {
                    failed = new BatchUpdateException(ex.getMessage(),
                            ex.getSQLState(), ex.getErrorCode(), result);
                    failed.initCause(ex);
                }
                failed.setNextException(ex);
            }
        }
        batches.clear();
        if (failed != null) {
            throw failed;
        }
        return result;
    }

    public void setCharacterStream(int parameterIndex, Reader reader, int length)
            throws SQLException {
        checkStatement();
        /**
         * @todo: Implement this java.sql.PreparedStatement method
         */
        throw new java.lang.UnsupportedOperationException(
                "Method setCharacterStream() not yet implemented.");
    }

    public void setRef(int i, Ref x) throws SQLException {
        checkStatement();
        /**
         * @todo: Implement this java.sql.PreparedStatement method
         */
        throw new java.lang.UnsupportedOperationException(
                "Method setRef() not yet implemented.");
    }

    public void setBlob(int i, Blob x) throws SQLException {
        checkStatement();
        /**
         * @todo: Implement this java.sql.PreparedStatement method
         */
        throw new java.lang.UnsupportedOperationException(
                "Method setBlob() not yet implemented.");
    }

    public void setClob(int i, Clob x) throws SQLException {
        checkStatement();
        /**
         * @todo: Implement this java.sql.PreparedStatement method
         */
        throw new java.lang.UnsupportedOperationException(
                "Method setClob() not yet implemented.");
    }

    public void setArray(int i, Array x) throws SQLException {
        checkStatement();
        /**
         * @todo: Implement this java.sql.PreparedStatement method
         */
        throw new java.lang.UnsupportedOperationException(
                "Method setArray() not yet implemented.");
    }

    public ResultSetMetaData getMetaData() throws SQLException {
        checkStatement();
        if (cmd instanceof CommandSelect) {
            try {
                ((CommandSelect) cmd).compile(con);
                MoonResultSetMetaData metaData = new MoonResultSetMetaData();
                metaData.columns = cmd.columnExpressions;
                return metaData;
            } catch (Exception e) {
                throw MoonException.createFromException(e);
            }
        }
        return null;
    }

    public void setDate(int parameterIndex, Date x, Calendar cal)
            throws SQLException {
        checkStatement();
        /**
         * @todo: Implement this java.sql.PreparedStatement method
         */
        throw new java.lang.UnsupportedOperationException(
                "Method setDate() not yet implemented.");
    }

    public void setTime(int parameterIndex, Time x, Calendar cal)
            throws SQLException {
        checkStatement();

        throw new java.lang.UnsupportedOperationException(
                "Method setTime() not yet implemented.");
    }

    public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal)
            throws SQLException {
        checkStatement();

        throw new java.lang.UnsupportedOperationException(
                "Method setTimestamp() not yet implemented.");
    }

    public void setNull(int paramIndex, int sqlType, String typeName)
            throws SQLException {
        checkStatement();

        throw new java.lang.UnsupportedOperationException(
                "Method setNull() not yet implemented.");
    }

    @Override
    public void setURL(int parameterIndex, URL x) throws SQLException {
        checkStatement();

        throw new java.lang.UnsupportedOperationException(
                "Method setURL() not yet implemented.");
    }

    @Override
    public ParameterMetaData getParameterMetaData() throws SQLException {
        checkStatement();

        throw new java.lang.UnsupportedOperationException(
                "Method getParameterMetaData() not yet implemented.");
    }

    @Override
    public boolean isClosed() throws SQLException {

        return false;
    }

    @Override
    public void setPoolable(boolean poolable) throws SQLException {

    }

    @Override
    public boolean isPoolable() throws SQLException {

        return false;
    }

    @Override
    public void closeOnCompletion() throws SQLException {

    }

    @Override
    public boolean isCloseOnCompletion() throws SQLException {

        return false;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {

        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {

        return false;
    }

    @Override
    public void setRowId(int parameterIndex, RowId x) throws SQLException {

    }

    @Override
    public void setNString(int parameterIndex, String value)
            throws SQLException {

    }

    @Override
    public void setNCharacterStream(int parameterIndex, Reader value,
            long length) throws SQLException {

    }

    @Override
    public void setNClob(int parameterIndex, NClob value) throws SQLException {

    }

    @Override
    public void setClob(int parameterIndex, Reader reader, long length)
            throws SQLException {

    }

    @Override
    public void setBlob(int parameterIndex, InputStream inputStream, long length)
            throws SQLException {

    }

    @Override
    public void setNClob(int parameterIndex, Reader reader, long length)
            throws SQLException {

    }

    @Override
    public void setSQLXML(int parameterIndex, SQLXML xmlObject)
            throws SQLException {

    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream x, long length)
            throws SQLException {

    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream x, long length)
            throws SQLException {

    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader reader,
            long length) throws SQLException {

    }

    @Override
    public void setAsciiStream(int parameterIndex, InputStream x)
            throws SQLException {

    }

    @Override
    public void setBinaryStream(int parameterIndex, InputStream x)
            throws SQLException {

    }

    @Override
    public void setCharacterStream(int parameterIndex, Reader reader)
            throws SQLException {

    }

    @Override
    public void setNCharacterStream(int parameterIndex, Reader value)
            throws SQLException {

    }

    @Override
    public void setClob(int parameterIndex, Reader reader) throws SQLException {

    }

    @Override
    public void setBlob(int parameterIndex, InputStream inputStream)
            throws SQLException {

    }

    @Override
    public void setNClob(int parameterIndex, Reader reader) throws SQLException {

    }
}
